package com.gusto.engine.recommend.prediction;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.gusto.engine.colfil.Distance;
import com.gusto.engine.colfil.Rating;
import com.gusto.engine.recommend.PredictionService;
import com.gusto.engine.recommend.prediction.base.BaseHybridImpl;

/**
 * <p>Neighborhood combination algorithm, 
 * combines neighborhoods (collaborative and semantic) 
 * and computes the prediction.</p>
 * 
 * @author amokrane.belloui@gmail.com
 *
 */
public class HybridCombinationNeighborhood extends BaseHybridImpl implements PredictionService {
	
	private Logger log = Logger.getLogger(getClass());
		
	protected Double doPrediction(long userId, long itemId, 
			List<Distance> userNeighborhoodCol,
			List<Distance> itemNeighborhoodCol,
			List<Distance> userNeighborhoodSem,
			List<Distance> itemNeighborhoodSem) {
		
		// Neighborhood Combination
		List<Distance> users = combination(userNeighborhoodCol, userNeighborhoodSem);
		List<Distance> items = combination(itemNeighborhoodCol, itemNeighborhoodSem);

		// Prediction
		Map<Long, Double> usersWeights = this.buildUserWeights(userId, users);
		Map<Long, Double> itemsWeights = this.buildItemWeights(itemId, items);

		List<Rating> evals = collaborativeService.getSubMatrix(usersWeights
				.keySet(), itemsWeights.keySet());

		double user_mean = collaborativeService.getUserMeanRating(userId);
		double item_mean = collaborativeService.getItemMeanRating(itemId);

		Double val = null;
		if (evals.size() >= params.getMinEvals()) {
			val = this.buildPrediction(evals, user_mean, item_mean, usersWeights, itemsWeights);
		}
		
		log.debug("Hybrid Combination (hyb:" + nbHybrid + ", col:" + nbCollaborative + ", sem:" + nbSemantic + ")");
		
		return val;
	}
	
	public List<Distance> combination(List<Distance> distances1, List<Distance> distances2) {
		Map<String, Distance> map1 = new HashMap<String, Distance>();
		Map<String, Distance> map2 = new HashMap<String, Distance>();
		
		for (Distance d : distances1) {
			String key = (d.getId1().compareTo(d.getId2()) < 0 ? 
												d.getId1() + "-" + d.getId2() : 
												d.getId2() + "-" + d.getId1());
			map1.put(key, d);
		}
		for (Distance d : distances2) {
			String key = (d.getId1().compareTo(d.getId2()) < 0 ? 
												d.getId1() + "-" + d.getId2() : 
												d.getId2() + "-" + d.getId1());
			map2.put(key, d);
		}
		
		for (String key : map2.keySet()) {
			if (map1.containsKey(key)) {
				Distance dA = map1.get(key);
				Distance dB = map2.get(key);
				
				Double dist = (dA.getDistance() + dB.getDistance()) / 2;
				Distance d = new Distance(dA.getId1(), dA.getId2(), 
						                  dA.getCount() + dB.getCount(), dist);
				
				map1.put(key, d);
			}
			else {
				map1.put(key, map2.get(key));
			}
		}
		
		return new ArrayList<Distance>(map1.values());
	}
	
}
